package fr.ul.ade.prime.service;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Locale;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.BaseColumns;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
import android.support.v4.app.TaskStackBuilder;
import fr.ul.ade.R;
import fr.ul.ade.prime.model.AdeWebOptions;
import fr.ul.ade.prime.model.Lesson;
import fr.ul.ade.prime.model.Schedule;
import fr.ul.ade.prime.model.ScheduleUpdater;
import fr.ul.ade.prime.model.ScheduleUpdater.ChangeType;
import fr.ul.ade.prime.provider.AdeWebContract;
import fr.ul.ade.prime.ui.BundleExtraNames;
import fr.ul.ade.prime.ui.LessonDetails;
import fr.ul.ade.prime.ui.LessonList.LessonBundle;
import fr.ul.ade.prime.ui.ScheduleList;
import fr.ul.ade.prime.ui.ViewSchedule;
import fr.ul.ade.prime.util.Lists;

/**
 * Classe responsable de la génération des notifications ainsi que de leur
 * suppression.
 * 
 * @author Schnell Michaël
 * @version 1.0
 */
public abstract class AdeWebNotificationManager {

	public static final String SCHEDULE_NOTIFICATION_TAG = "SCHEDULE_NOTIFICATION"; //$NON-NLS-1$

	public static final String FAILED_AUTO_UPDATE_TAG = "FAILED_AUTO_UPDATE"; //$NON-NLS-1$

	@SuppressWarnings("unused")
	private static final String TAG = AdeWebNotificationManager.class
			.getSimpleName();

	private static final String NOTIFICATION_PREFS_NAME = "fr.ul.ade.prime.Notification"; //$NON-NLS-1$

	private static final int NOTIFICATION_ID = 0x42;

	private static final int MAX_N_LESSON_CHANGES = 6;

	private static final int MAX_N_SCHEDULE_CHANGES = 6;

	private static final String NOTIFICATION_NUMBER = "fr.ul.ade.prime.NOTIFICATION_NUMBER"; //$NON-NLS-1$

	//	private static final String NOTIFICATION_ISSUED = "fr.ul.ade.prime.NOTIFICATION_ISSUED"; //$NON-NLS-1$

	private static final int NOTIFICATION_SMALL_ICON = R.drawable.ul_notify;

	private static final int NOTIFICATION_LARGE_ICON = R.drawable.icon;

	private static final String DATE_FORMAT = "E d MMM à H'h'mm"; //$NON-NLS-1$

	private static String scheduleName;

	protected AdeWebNotificationManager() {
	}// NotificationManager()

	/**
	 * Crée éventuellement une notification pour indiquer à l'utilisateur que la
	 * sychronisation automatique n'a pas réussi à mettre à jour des emplois du
	 * temps depuis 3 jours.
	 */
	public synchronized static void notifyScheduleUpdateFailed(Context context) {
		Resources resources = context.getResources();
		Bitmap largeIcon = BitmapFactory.decodeResource(resources,
				NOTIFICATION_LARGE_ICON);
		NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
				context)
				.setLargeIcon(largeIcon)
				.setSmallIcon(NOTIFICATION_SMALL_ICON)
				.setContentTitle(resources.getString(R.string.Notification_auto_update_failed_title))
				.setContentText(resources.getString(R.string.Notification_auto_update_failed_text))
				.setAutoCancel(true);

		// Creates an explicit intent for an Activity in your app
		Intent scheduleListIntent = new Intent(context, ScheduleList.class);

		// The stack builder object will contain an artificial back stack for
		// the started Activity. This ensures that navigating backward from the
		// Activity leads out of your application to the Home screen.
		TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
		// Adds the Intent that starts the Activity to the top of the stack
		stackBuilder.addNextIntent(scheduleListIntent);
		PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
				PendingIntent.FLAG_UPDATE_CURRENT);
		mBuilder.setContentIntent(resultPendingIntent);

		NotificationManager mNotificationManager = (NotificationManager) context
				.getSystemService(Context.NOTIFICATION_SERVICE);
		mNotificationManager.notify(FAILED_AUTO_UPDATE_TAG,
				NOTIFICATION_ID,
				mBuilder.build());
	}// notifyScheduleUpdateFailed(Context)

	/**
	 * Crée éventuellement une notification pour l'emploi du temps indiqué en
	 * prennant en compte les modifications trouvées par l'updater. S'il n'y a
	 * pas de modification importante, aucune notification n'est créée.
	 * 
	 * @param scheduleId
	 *            identifiant de l'emploi du temps modifié
	 * @param updater
	 *            résultats de la comparaison avec l'ancien emploi du temps
	 */
	public synchronized static void notifyImportantScheduleChange(
			Context context, long scheduleId, ScheduleUpdater updater) {
		if (!AdeWebOptions.notifyChangesIsOn(context)) {
			return;
		}// if
		if (updater.importantChange()) {
			SharedPreferences preferences = context
					.getSharedPreferences(NOTIFICATION_PREFS_NAME,
							Context.MODE_PRIVATE);
			scheduleName = null;

			if (preferences.getInt(NOTIFICATION_NUMBER, 0) > 0) {
				buildBigNotification(context, scheduleId, updater);
			} else {
				buildSmallNotification(context, scheduleId, updater);
			}// if

			if (scheduleName != null) {
				int notifNumber = preferences.getInt(NOTIFICATION_NUMBER, 0);
				preferences.edit().putInt(NOTIFICATION_NUMBER, notifNumber + 1)
						.putString(Long.toString(notifNumber), scheduleName)
						.commit();
			}// if
		}// if
	}// notifyImportantScheduleChange(Context, long, ScheduleUpdater)

	/**
	 * Suppprime l'éventuelle notification pour l'emploi du temps indiqué en
	 * prennant.
	 * 
	 * @param scheduleId
	 *            identifiant de l'emploi du temps modifié
	 */
	public static void removeNotificationForImportantScheduleChange(
			Context context, String tag) {
		SharedPreferences preferences = context
				.getSharedPreferences(NOTIFICATION_PREFS_NAME,
						Context.MODE_PRIVATE);
		preferences.edit().putInt(NOTIFICATION_NUMBER, 0).commit();
		NotificationManager mNotificationManager = (NotificationManager) context
				.getSystemService(Context.NOTIFICATION_SERVICE);
		mNotificationManager.cancel(tag, NOTIFICATION_ID);
	}// removeNotificationForImportantScheduleChange(Context, long)

	private static void buildBigNotification(Context context, long scheduleId,
			ScheduleUpdater updater) {
		final Uri scheduleUri = ContentUris
				.withAppendedId(AdeWebContract.Schedule.CONTENT_URI, scheduleId);
		final Cursor cursor = context.getContentResolver().query(scheduleUri,
				ScheduleQuery.PROJECTION,
				null,
				null,
				AdeWebContract.Schedule.DEFAULT_SORT);
		if (!cursor.moveToNext()
				|| (cursor.getInt(ScheduleQuery.SCHEDULE_NOTIFY_UPDATE) == Schedule.NOTIFY_FALSE)) {
			scheduleName = null;
			return;
		}// if

		SharedPreferences preferences = context
				.getSharedPreferences(NOTIFICATION_PREFS_NAME,
						Context.MODE_PRIVATE);
		int nModifiedSchedules = preferences.getInt(NOTIFICATION_NUMBER, 0);
		NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
		int nDisplayedSchedules = Math.min(MAX_N_SCHEDULE_CHANGES,
				nModifiedSchedules);
		scheduleName = cursor.getString(ScheduleQuery.SCHEDULE_NAME);
		boolean alreadyNotified = false;
		for (int schedule = 0; schedule < nDisplayedSchedules; schedule++) {
			String name = preferences.getString(Integer.toString(schedule),
					scheduleName);
			alreadyNotified = alreadyNotified || name.equals(scheduleName);
			inboxStyle.addLine(name);
		}// for
		if (!alreadyNotified) {
			++nModifiedSchedules;
			inboxStyle.addLine(scheduleName);
		} else {
			scheduleName = null;
		}// if

		Resources resources = context.getResources();
		Bitmap largeIcon = BitmapFactory.decodeResource(resources,
				NOTIFICATION_LARGE_ICON);
		String contentTitle = resources
				.getString(R.string.Notification_multiple_schedule_changes,
						Integer.valueOf(nModifiedSchedules));
		inboxStyle.setBigContentTitle(contentTitle);
		NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
				context).setLargeIcon(largeIcon)
				.setSmallIcon(NOTIFICATION_SMALL_ICON)
				.setContentTitle(contentTitle).setNumber(nModifiedSchedules)
				.setAutoCancel(true).setStyle(inboxStyle);

		// Creates an explicit intent for an Activity in your app
		Intent scheduleListIntent = new Intent(context, ScheduleList.class);

		// The stack builder object will contain an artificial back stack for
		// the started Activity. This ensures that navigating backward from the
		// Activity leads out of your application to the Home screen.
		TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
		// Adds the Intent that starts the Activity to the top of the stack
		stackBuilder.addNextIntent(scheduleListIntent);
		PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
				PendingIntent.FLAG_UPDATE_CURRENT);
		mBuilder.setContentIntent(resultPendingIntent);
		cursor.close();
		NotificationManager mNotificationManager = (NotificationManager) context
				.getSystemService(Context.NOTIFICATION_SERVICE);
		mNotificationManager.notify(SCHEDULE_NOTIFICATION_TAG,
				NOTIFICATION_ID,
				mBuilder.build());
	}// buildSingleScheduleNotification(Context, long, ScheduleUpdater)

	private static void buildSmallNotification(Context context,
			long scheduleId, ScheduleUpdater updater) {
		NotificationCompat.Builder mBuilder = null;
		if (updater.singleLessonChange()) {
			mBuilder = buildSingleLessonNotification(context,
					scheduleId,
					updater);
		} else {
			mBuilder = buildSingleScheduleNotification(context,
					scheduleId,
					updater);
		}// if
		if (mBuilder == null) {
			scheduleName = null;
			return;
		}// if
		NotificationManager mNotificationManager = (NotificationManager) context
				.getSystemService(Context.NOTIFICATION_SERVICE);
		mNotificationManager.notify(SCHEDULE_NOTIFICATION_TAG,
				NOTIFICATION_ID,
				mBuilder.build());
	}// / buildNotification(Context, long, ScheduleUpdater)

	private static Builder buildSingleScheduleNotification(Context context,
			long scheduleId, ScheduleUpdater updater) {
		final Uri scheduleUri = ContentUris
				.withAppendedId(AdeWebContract.Schedule.CONTENT_URI, scheduleId);
		final Cursor cursor = context.getContentResolver().query(scheduleUri,
				ScheduleQuery.PROJECTION,
				null,
				null,
				AdeWebContract.Schedule.DEFAULT_SORT);
		if (!cursor.moveToNext()
				|| (cursor.getInt(ScheduleQuery.SCHEDULE_NOTIFY_UPDATE) == Schedule.NOTIFY_FALSE)) {
			return null;
		}// if

		scheduleName = cursor.getString(ScheduleQuery.SCHEDULE_NAME);
		int schedulePeriod = cursor.getInt(ScheduleQuery.SCHEDULE_PERIOD);
		Resources resources = context.getResources();
		Bitmap largeIcon = BitmapFactory.decodeResource(resources,
				NOTIFICATION_LARGE_ICON);
		NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
				context)
				.setLargeIcon(largeIcon)
				.setSmallIcon(NOTIFICATION_SMALL_ICON)
				.setContentTitle(scheduleName)
				.setContentText(resources.getString(R.string.Notification_single_schedule_multiple_changes))
				.setNumber(updater.getNModifiedLessons()).setAutoCancel(true);

		NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
		inboxStyle.setBigContentTitle(scheduleName);
		inboxStyle
				.setSummaryText(resources
						.getString(R.string.Notification_single_schedule_multiple_changes));
		int nLessonChanges = Math.min(MAX_N_LESSON_CHANGES,
				updater.getNModifiedLessons());
		for (int l = 0; l < nLessonChanges; l++) {
			Lesson modified = updater.getModifiedLesson(l);
			String change;
			// Log.d(TAG,
			// String.format("%s - %s",
			// modified,
			// updater.getChangeType(modified)));
			switch (updater.getChangeType(modified)) {
			case INSERT: {
				change = resources
						.getString(R.string.Notification_single_schedule_lesson_added);
				break;
			}
			case TIME_UPDATE: {
				change = resources
						.getString(R.string.Notification_single_schedule_lesson_moved);
				break;
			}
			case DELETE: {
				change = resources
						.getString(R.string.Notification_single_schedule_lesson_canceled);
				break;
			}
			default: {
				change = modified.getRoom();
			}
			}// switch
			inboxStyle.addLine(String.format("%s - %s", //$NON-NLS-1$
					modified.getTitle(),
					change));
		}// for
		mBuilder.setStyle(inboxStyle);

		// Creates an explicit intent for an Activity in your app
		Intent viewScheduleIntent = new Intent(context, ViewSchedule.class);
		viewScheduleIntent.putExtra(BundleExtraNames.EXTRA_SCHEDULE_ID,
				scheduleId);
		viewScheduleIntent.putExtra(BundleExtraNames.EXTRA_SCHEDULE_NAME,
				scheduleName);
		viewScheduleIntent.putExtra(BundleExtraNames.EXTRA_SCHEDULE_PERIOD,
				schedulePeriod);

		// The stack builder object will contain an artificial back stack for
		// the started Activity. This ensures that navigating backward from the
		// Activity leads out of your application to the Home screen.
		TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
		// Adds the back stack for the Intent (but not the Intent itself)
		stackBuilder.addParentStack(ViewSchedule.class);
		// Adds the Intent that starts the Activity to the top of the stack
		stackBuilder.addNextIntent(viewScheduleIntent);
		PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
				PendingIntent.FLAG_UPDATE_CURRENT);
		mBuilder.setContentIntent(resultPendingIntent);
		cursor.close();
		return mBuilder;
	}// buildSingleScheduleNotification(Context, long, ScheduleUpdater)

	private static Builder buildSingleLessonNotification(Context context,
			long scheduleId, ScheduleUpdater updater) {
		final Uri scheduleUri = ContentUris
				.withAppendedId(AdeWebContract.Schedule.CONTENT_URI, scheduleId);
		final Cursor scheduleCursor = context.getContentResolver()
				.query(scheduleUri,
						ScheduleQuery.PROJECTION,
						null,
						null,
						AdeWebContract.Schedule.DEFAULT_SORT);
		if (!scheduleCursor.moveToNext()
				|| (scheduleCursor.getInt(ScheduleQuery.SCHEDULE_NOTIFY_UPDATE) == Schedule.NOTIFY_FALSE)) {
			return null;
		}// if

		Lesson modifiedLesson = updater.getModifiedLesson();
		scheduleName = scheduleCursor.getString(ScheduleQuery.SCHEDULE_NAME);
		int schedulePeriod = scheduleCursor
				.getInt(ScheduleQuery.SCHEDULE_PERIOD);
		Resources resources = context.getResources();
		Bitmap largeIcon = BitmapFactory.decodeResource(resources,
				NOTIFICATION_LARGE_ICON);
		String contentText = new SimpleDateFormat(DATE_FORMAT, Locale.FRANCE)
				.format(modifiedLesson.getStartDateTime());
		contentText = String.format("%C%s", //$NON-NLS-1$
				Character.valueOf(contentText.charAt(0)),
				contentText.substring(1));
		String contentInfo;
		switch (updater.getChangeType()) {
		case INSERT: {
			contentInfo = resources
					.getString(R.string.Notification_single_schedule_lesson_added);
			break;
		}
		case TIME_UPDATE: {
			contentInfo = resources
					.getString(R.string.Notification_single_schedule_lesson_moved);
			break;
		}
		case DELETE: {
			contentInfo = resources
					.getString(R.string.Notification_single_schedule_lesson_canceled);
			break;
		}
		default: {
			contentInfo = modifiedLesson.getRoom();
		}
		}// switch
		NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
				context).setLargeIcon(largeIcon)
				.setSmallIcon(NOTIFICATION_SMALL_ICON)
				.setContentTitle(modifiedLesson.getTitle())
				.setContentText(contentText).setContentInfo(contentInfo)
				.setAutoCancel(true);
		// Creates an explicit intent for an Activity in your app
		Intent resultIntent = null;
		Class<?> targetActivity = null;
		if (updater.getChangeType().equals(ChangeType.DELETE)) {
			targetActivity = ViewSchedule.class;
			resultIntent = new Intent(context, targetActivity);
			resultIntent.putExtra(BundleExtraNames.EXTRA_SCHEDULE_ID,
					scheduleId);
			resultIntent.putExtra(BundleExtraNames.EXTRA_SCHEDULE_NAME,
					scheduleName);
			resultIntent.putExtra(BundleExtraNames.EXTRA_SCHEDULE_PERIOD,
					schedulePeriod);
		} else {
			targetActivity = LessonDetails.class;
			final Uri lessonUri = AdeWebContract.Lesson
					.buildScheduleUri(scheduleId);
			final Cursor lessonsCursor = context.getContentResolver()
					.query(lessonUri,
							LessonQuery.PROJECTION,
							null,
							null,
							AdeWebContract.Lesson.DEFAULT_SORT);
			ArrayList<LessonBundle> lessons = Lists.newArrayList();
			while (lessonsCursor.moveToNext()) {
				lessons.add(new LessonBundle(lessonsCursor
						.getLong(LessonQuery._ID)));
			}// while
			resultIntent = new Intent(context, targetActivity);
			resultIntent.putExtra(BundleExtraNames.EXTRA_SCHEDULE_ID,
					scheduleId);
			resultIntent.putExtra(BundleExtraNames.EXTRA_SCHEDULE_NAME,
					scheduleName);
			resultIntent.putExtra(BundleExtraNames.EXTRA_DISPLAYED_LESSON,
					modifiedLesson.getLessonId());
			resultIntent.putExtra(BundleExtraNames.EXTRA_LESSON_NAME,
					modifiedLesson.getTitle());
			resultIntent
					.putParcelableArrayListExtra(BundleExtraNames.EXTRA_SCHEDULE_LESSONS,
							lessons);
		}// if

		// The stack builder object will contain an artificial back stack for
		// the started Activity. This ensures that navigating backward from the
		// Activity leads out of your application to the Home screen.
		TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
		// Adds the back stack for the Intent (but not the Intent itself)
		stackBuilder.addParentStack(targetActivity);
		// Adds the Intent that starts the Activity to the top of the stack
		stackBuilder.addNextIntent(resultIntent);
		PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0,
				PendingIntent.FLAG_UPDATE_CURRENT);
		mBuilder.setContentIntent(resultPendingIntent);
		scheduleCursor.close();
		return mBuilder;
	}// buildSingleLessonNotification(Context, long, ScheduleUpdater)

	private interface ScheduleQuery {
		String[] PROJECTION = { BaseColumns._ID,
				AdeWebContract.Schedule.SCHEDULE_NAME,
				AdeWebContract.Schedule.SCHEDULE_PERIOD,
				AdeWebContract.Schedule.SCHEDULE_NOTIFY_UPDATE };

		// int _ID = 0;
		int SCHEDULE_NAME = 1;
		int SCHEDULE_PERIOD = 2;
		int SCHEDULE_NOTIFY_UPDATE = 3;
	}// interface ScheduleQuery

	private interface LessonQuery {
		String[] PROJECTION = { BaseColumns._ID };

		int _ID = 0;
	}// interface ScheduleQuery

}// class NotificationManager